using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

namespace GuavaData
{
    public enum JoinType { INNER, LEFT, RIGHT, FULL }
    
    public class Join : Operator
    {
        // Relational algebra join operator: child1 |X| child2
        // This operator has outer join variants

        #region Fields
        Operator child1;
        public Operator Child1
        {
            get { return child1; }
            set { child1 = value; }
        }

        Operator child2;
        public Operator Child2
        {
            get { return child2; }
            set { child2 = value; }
        }

        List<string> leftColumns;
        public List<string> LeftColumns
        {
            get { return leftColumns; }
            set { leftColumns = value; }
        }

        List<Condition> rightConditions;
        public List<Condition> RightConditions
        {
            get { return rightConditions; }
            set { rightConditions = value; }
        }

        JoinType type;
        public JoinType Type
        {
            get { return type; }
            set { type = value; }
        }
        #endregion Fields

        public Join(Operator child1, Operator child2, JoinType jt)
        {
            this.child1 = child1;
            this.child2 = child2;
            child1.parent = this;
            child2.parent = this;
            this.leftColumns = new List<string>();
            this.rightConditions = new List<Condition>();
            this.type = jt;

            // List of columns and domains is equal to the combination of the two from the base expressions
            this.exposedColumns = new List<string>(this.child1.exposedColumns);
            this.exposedDomains = new List<GuavaData.Domain.Domain>(this.child1.exposedDomains);
            for (int i = 0; i < this.child2.exposedColumns.Count; i++)
            {
                // Ignore duplicate columns
                if (this.exposedColumns.Contains(this.child2.exposedColumns[i]))
                    continue;

                this.exposedColumns.Add(this.child2.exposedColumns[i]);
                this.exposedDomains.Add(this.child2.exposedDomains[i]);
            }
        }

        public Join(Operator child1, Operator child2, IEnumerable<string> leftColumns, IEnumerable<Condition> rightConditions, JoinType jt)
        {
            this.child1 = child1;
            this.child2 = child2;
            child1.parent = this;
            child2.parent = this;
            this.leftColumns = new List<string>(leftColumns);
            this.rightConditions = new List<Condition>(rightConditions);
            this.type = jt;

            // List of columns and domains is equal to the combination of the two from the base expressions
            this.exposedColumns = new List<string>(this.child1.exposedColumns);
            this.exposedDomains = new List<GuavaData.Domain.Domain>(this.child1.exposedDomains);
            for (int i = 0; i < this.child2.exposedColumns.Count; i++)
            {
                // Ignore duplicate columns
                if (this.exposedColumns.Contains(this.child2.exposedColumns[i]))
                    continue;

                this.exposedColumns.Add(this.child2.exposedColumns[i]);
                this.exposedDomains.Add(this.child2.exposedDomains[i]);
            }
        }

        public override SqlText Visit(Provider qv)
        {
            return qv.Transform(this);
        }
        public override void Visit(Transform t, Command c)
        {
            t.Expand(this, c);
        }

        public override Operator Clone()
        {
            return new Join(this.child1.Clone(), this.child2.Clone(), this.leftColumns, this.rightConditions, this.type);
        }

        public void AddCondition(string leftColumn, Condition rightCondition)
        {
            this.leftColumns.Add(leftColumn);
            this.rightConditions.Add(rightCondition);
        }

        public string JoinCondition
        {
            get
            {
                if (this.leftColumns.Count <= 0)
                    throw new Exception("Cannot create a join condition with no conditions.");

                // Build the join condition
                string build = this.leftColumns[0] + this.rightConditions[0].ToString();
                for (int i = 1; i < this.leftColumns.Count; i++)
                    build = String.Concat(build, " AND ", this.leftColumns[0], this.rightConditions[0].ToString());
                return build;
            }
        }

            public override void NotifyChange(Operator old, Operator newOp)
        {
            if (this.child1 == old)
            {
                this.child1 = newOp;
                newOp.parent = this;
            }
            if (this.child2 == old)
            {
                this.child2 = newOp;
                newOp.parent = this;
            }
        }

            public override void Visit(Channel c, string table)
            {
                c.MapAliases(this, table);
            }

            new public static Join FromXElement(XElement x)
            {

                Operator child1 = null;
                Operator child2 = null;
                List<string> leftColumns = new List<string>();
                List<string> rightColumns = new List<string>();
                string parentKey = x.Attribute("parentKey").Value;
                string childKey = x.Attribute("childKey").Value;
                foreach (XElement att in x.Elements())
                {
                    if (child1 == null) child1 = Operator.FromXElement(att);
                    else child2 = Operator.FromXElement(att);
                }

                if (!(child1 == null && child2 == null))
                {
                    Join working = new Join(child1, child2, JoinType.FULL);
                    (working as Join).AddCondition(parentKey, Condition.NewCondition(ConditionType.EQ, new ColumnReference(childKey)));
                    return working;
                }
                else
                    throw new Exception("While loading Join from XML: children were null");
            }
    }
}
